﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace CommonAsyncDemoFunctions
{
	public static class FileLoader
	{
		const int bufferSize = 1; //Slow processing down for illustration
		static string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

		static object locker = new object(); //Simple lock for safe multi-threaded access to the static fortunes list
		static List<string> fortunes = new List<string>();
		static List<string> filesToLoad = new List<string>(){"ContentFiles\\art.txt",
																													 "ContentFiles\\computers.txt",
																													 "ContentFiles\\fortunes.txt",
																													 "ContentFiles\\science.txt"};

		async static Task LoadFortunesFromFileAsync(string filename)
		{
			var stream = File.OpenRead(filename);
			char[] charBuffer = new char[bufferSize];
			int bytesRead = 0;
			string nextfortune = "";

			using (var reader = new StreamReader(stream))
			{
				bytesRead = await reader.ReadBlockAsync(charBuffer, 0, bufferSize);
				while (bytesRead == bufferSize)
				{
					string fortunebuffer = new String(charBuffer);
					var fortunesInBuffer = fortunebuffer.Split('%');
					fortunesInBuffer[0] = nextfortune + fortunesInBuffer[0];
					for (int i = 0; i < fortunesInBuffer.Length - 1; i++)
						AddFortune(fortunesInBuffer[i]);
					nextfortune = fortunesInBuffer[fortunesInBuffer.Length - 1];
					bytesRead = await reader.ReadBlockAsync(charBuffer, 0, bufferSize);
				}
				AddFortune(nextfortune);
			}
		}

		static void LoadFortunesFromFile(string filename)
		{
			var stream = File.OpenRead(filename);
			char[] charBuffer = new char[bufferSize];
			int bytesRead = 0;
			string nextfortune = "";

			using (var reader = new StreamReader(stream))
			{
				bytesRead = reader.ReadBlock(charBuffer, 0, bufferSize);
				while (bytesRead == bufferSize)
				{
					string fortunebuffer = new String(charBuffer);
					var fortunesInBuffer = fortunebuffer.Split('%');
					fortunesInBuffer[0] = nextfortune + fortunesInBuffer[0];
					for (int i = 0; i < fortunesInBuffer.Length - 1; i++)
						AddFortune(fortunesInBuffer[i]);
					nextfortune = fortunesInBuffer[fortunesInBuffer.Length - 1];
					bytesRead = reader.ReadBlock(charBuffer, 0, bufferSize);
				}
				AddFortune(nextfortune);
			}
		}

		private static void AddFortune(string fortuneText)
		{
			lock (locker)
			{
				fortunes.Add(fortuneText);
			}
		}

		private static void ClearFortunes()
		{
			lock (locker)
			{
				fortunes.Clear();
			}
		}

		//Load all fortune files at the same time
		public static Task ReloadFortunes() 
		{
			ClearFortunes();

			var tasks = filesToLoad.Select(file => LoadFortunesFromFileAsync(Path.Combine(assemblyFolder, file)));
			//The Above is equivalent to this (if tasks was an array)
				//tasks[0] = LoadFortunesFromFileAsync(Path.Combine(assemblyFolder, "ContentFiles\\art.txt"));
				//tasks[1] = LoadFortunesFromFileAsync(Path.Combine(assemblyFolder, "ContentFiles\\computers.txt"));
				//tasks[2] = LoadFortunesFromFileAsync(Path.Combine(assemblyFolder, "ContentFiles\\fortunes.txt"));
				//tasks[3] = LoadFortunesFromFileAsync(Path.Combine(assemblyFolder, "ContentFiles\\science.txt"));
			
			
			return Task.WhenAll(tasks);
		}

		//Load each file on this thread one at a time
		public static void ReloadFortunesSynchronous()
		{
			ClearFortunes();
			filesToLoad.ForEach(file =>
				{
					Thread.Sleep(1000); //This is so that this will take extra time for illustration
					LoadFortunesFromFile(Path.Combine(assemblyFolder, file));
				});
		}

		//Reload fortunes files on a separate thread one at a time
		public async static Task ReloadFortunesAsyncSequential()
		{
			foreach (var file in filesToLoad)
			{
				await LoadFortunesFromFileAsync(Path.Combine(assemblyFolder, file));
			}
		}

		//Reload files one at a time a separate thread with continuations
		public async static Task ReloadFortunesAsyncContinue()
		{
			await LoadFortunesFromFileAsync(Path.Combine(assemblyFolder, "ContentFiles\\art.txt"))
				.ContinueWith(t => LoadFortunesFromFile(Path.Combine(assemblyFolder, "ContentFiles\\computers.txt")))
				.ContinueWith(t => LoadFortunesFromFile(Path.Combine(assemblyFolder, "ContentFiles\\fortunes.txt")))
				.ContinueWith(t => LoadFortunesFromFile(Path.Combine(assemblyFolder, "ContentFiles\\science.txt")));
		}

		//Load each file on another thread one at a time, with a cancellation check
		public async static Task ReloadFortunesCancelable(CancellationTokenSource tokensource)
		{
			ClearFortunes();

			var token = tokensource.Token;

			foreach (var file in filesToLoad)
			{
				await Task.Delay(1000); //This is so that this will take extra time for illustration
				token.ThrowIfCancellationRequested(); //check for cancellation from the task creator
				await LoadFortunesFromFileAsync(Path.Combine(assemblyFolder, file));
			}
		}
		
		//Reload fortune files in a parallel for each loop (Concurrently on separate threads)
		public static void ReloadFortunesParallelLoop()
		{
			var result = Parallel.ForEach(filesToLoad, (file) => { LoadFortunesFromFile(Path.Combine(assemblyFolder, file)); });
		}



		public async static Task<string> GetFortuneAsync()
		{
			if (fortunes.Count <= 0) await ReloadFortunes();
			if (fortunes.Count <= 0) throw new ApplicationException("Unable to Load Fortunes");
			var gen = new Random();
			var index = gen.Next(fortunes.Count - 1);
			return fortunes.ToArray()[index];
		}

		public static string GetFortune()
		{
			if (fortunes.Count <= 0) throw new ApplicationException("No Loaded Fortunes");
			var gen = new Random();
			var index = gen.Next(fortunes.Count - 1);
			return fortunes.ToArray()[index];
		}

		public static int GetCount()
		{
			return fortunes.Count;
		}
	}
}
